local _M = {}
local bit = require 'bit'

local ffi = require("ffi")

local tohex = bit.tohex
local band = bit.band
local bor = bit.bor
local rshift = bit.rshift
local lshift = bit.lshift

local bytes =  ffi.typeof('uint8_t[?]')
local uint32_t =  ffi.typeof('uint32_t')
local new = ffi.new

local math = math
local fmt = string.format
local ngx = ngx
local table = table


local _M = {
    _VERSION = '0.0.1'
}

local localIP

local get_local_ip = function()
    local localIP = ''
    local ok, socket = pcall(require, "socket")
    local function GetAdd(hostname)
        local ip, resolved = socket.dns.toip(hostname)
        local ListTab = {}
        if resolved and "table" == type(resolved) then
            for k, v in ipairs(resolved.ip) do
                table.insert(ListTab, v)
            end
        end
        return ListTab
    end
    if ok then
        local ip = GetAdd(socket.dns.gethostname())
        if ip and "table" == type(ip) and next(ip) and unpack(ip) then
            localIP = unpack(ip)
        end
    end

    return localIP
end

localIP = get_local_ip()

local random = math.random

function _M.new()
    local tab = {}
    local start_time = 1640966400
    tab.last_time = ngx.time() - start_time
    tab.seq = new(uint32_t)
    tab.node = new(uint32_t)
    if localIP then
        -- 防止ngx.re.match在init-by-lua阶段执行报错
        local ok, capture, err = pcall(ngx.re.match, localIP, [[(\d+)\.(\d+)\.(\d+)\.(\d+)]])
        if not ok then
            capture = {}
            local i = 1
            for word in string.gmatch(localIP, "%d+") do
                capture[i] = word
                i = i + 1
            end
            capture[0] = table.concat(capture, ".")
        end
        if capture and capture[1] and capture[2] and capture[3] and capture[4] then
            local c1 = tonumber(capture[1]) or 0
            local c2 = tonumber(capture[2]) or 0
            local c3 = tonumber(capture[3]) or 0
            local c4 = tonumber(capture[4]) or 0
            tab.node = bor(tab.node, c4)
            tab.node = bor(tab.node, lshift(c3, 8))
            tab.node = bor(tab.node, lshift(c2, 16))
            tab.node = bor(tab.node, lshift(c1, 24))
        end
    end

    if tab.node == 0 then
        local c1 = random(0, 255)
        local c2 = random(0, 255)
        local c3 = random(0, 255)
        local c4 = random(0, 255)
        tab.node = bor(tab.node, c4)
        tab.node = bor(tab.node, lshift(c3, 8))
        tab.node = bor(tab.node, lshift(c2, 16))
        tab.node = bor(tab.node, lshift(c1, 24))
    end

    return setmetatable(tab, { __index = _M })
end

function _M:seed(seed)
    if not seed then
        if ngx then
            seed = ngx.time() + ngx.worker.pid()

        elseif package.loaded['socket'] and package.loaded['socket'].gettime then
            seed = package.loaded['socket'].gettime()*10000

        else
            seed = os.time()
        end
    end

    math.randomseed(seed)

    return seed
end

function _M.timestamp()
    return ngx.now() * 1000
end

function _M:uuid()
    if band(self.seq, 0xfff00000) ~= 0 then
        self.last_time = self.last_time + 1
        self.seq = 0
    else
        self.seq = self.seq + 1
    end

    local woker_pid = ngx.worker.pid() or random(65535)
    self.seq = self.seq + 1 


    return fmt("%s%s%s%s",
        tohex(self.last_time, 8),
        tohex(self.seq, 8),
        tohex(woker_pid, 4),
        tohex(self.node, 8)
    )
end

return _M
